/*
 * basic_http.h
 *
 *  Created on: 14-May-2011
 *      Author: tan-ce
 */

#ifndef BASIC_HTTP_H_
#define BASIC_HTTP_H_

#include <string>
#include <map>
#include <vector>
#include <time.h>

#include "tcetools/socket.h"
#include "md5.h"

namespace tcetools {

#define MAX_POST		(512 * 1024)	// Maximum size of POST data, in bytes
#define MAX_NONCE_AGE	(5 * 60)		// Maximum age of nonce allowed in seconds
#define MAX_SESSION_AGE	(60 * 60)		// Maximum idle time for a session, in seconds

class BasicHttp {
protected:
	BasicHttp() {}

public:
	typedef struct {
		std::string nonce;
		std::string last_nonce;
		std::string opaque;
		int last_count;
		time_t last_active;
		time_t nonce_age;
	} auth_session;

	typedef std::map<std::string, auth_session *> auth_sessions_map;
	typedef std::vector<char *> tokens;
	typedef tokens::iterator  tokens_iter;

	// Look for at most token_counts tokens. 0 or negative values means no limit
	static tokens tokenizer(char *text, char delim = ' ', int token_count = 0);

	typedef std::map<std::string, std::string> field_map;
	typedef field_map::const_iterator field_map_iter;

	// The c string returned must be deallocated manually
	static char *copy_string(std::string str) {
		char *buf = new char[str.length() + 1];
		str.copy(buf, str.length(), 0);
		buf[str.length()] = 0;
		return buf;
	}

	// Remove leading and trailing spaces
	// This function is destructive
	static char *trim_string(char *str);

	static void rand_chars(char *buf, size_t len);
	static void rand_chars16(char *buf) {
		rand_chars(buf, 16);
	}

	static std::string chars2hex(char *buf, size_t len);

	static std::string rand_hex16() {
		char buf[16];
		rand_chars16(buf);
	    return chars2hex(buf, 16);
	}
};

class BasicHttpDigestAuthInfo : public BasicHttp {
protected:
	std::string realm;

public:
	BasicHttpDigestAuthInfo(std::string realm_name) : realm(realm_name) {}

	field_map HA1;
	auth_sessions_map auth_sessions;

	std::string &get_realm() { return realm; }

	void add_user(std::string user, std::string password) {
		HA1[user] = MD5(user + ":" + realm + ":" + password).hexdigest();
	}

	void session_data_cleanup();
	size_t session_count() { return auth_sessions.size(); }
};

class BasicHttpServer : public BasicHttp {
public:
	enum RequestType {
		REQ_GET, REQ_POST
	};

	enum StatusCode {
		STATUS_CONTINUE = 100,
		STATUS_SWITCHING_PROTOCOLS = 101,
		STATUS_URI_TOO_LONG = 112,

		STATUS_OK = 200,
		STATUS_CREATED = 201,
		STATUS_ACCEPTED = 202,
		STATUS_NON_AUTH_INFO = 203,
		STATUS_NO_CONTENT = 204,
		STATUS_RESET_CONTENT = 205,
		STATUS_PARTIAL_CONTENT = 206,
		STATUS_IM_USED = 226,

		STATUS_MULTIPLE_CHOICES = 300,
		STATUS_MOVED_PERMANENTLY = 301,
		STATUS_FOUND = 302,
		STATUS_SEE_OTHER = 303,
		STATUS_NOT_MODIFIED = 304,
		STATUS_USE_PROXY = 305,
		STATUS_SWITCH_PROXY = 306,
		STATUS_TEMPORARY_REDIRECT = 307,

		STATUS_BAD_REQUEST = 400,
		STATUS_UNAUTHORIZED = 401,
		STATUS_PAYMENT_REQUIRED = 402,
		STATUS_FORBIDDEN = 403,
		STATUS_NOT_FOUND = 404,
		STATUS_METHOD_NOT_ALLOWED = 405,
		STATUS_NOT_ACCEPTABLE = 406,
		STATUS_PROXY_AUTHENTICATION_REQUIRED = 407,
		STATUS_REQUEST_TIMEOUT = 408,
		STATUS_CONFLICT = 409,
		STATUS_GONE = 410,
		STATUS_LENGTH_REQUIRED = 411,
		STATUS_PRECONDITION_FAILED = 412,
		STATUS_REQUEST_ENTITY_TOO_LARGE = 413,
		STATUS_REQUEST_URI_TOO_LONG = 414,
		STATUS_UNSUPPORTED_MEDIA_TYPE = 415,
		STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416,
		STATUS_EXPECTATION_FAILED = 417,
		STATUS_I_AM_TEAPOT = 418,

		STATUS_INTERNAL_SERVER_ERROR = 500,
		STATUS_NOT_IMPLEMENTED = 501,
		STATUS_BAD_GATEWAY = 502,
		STATUS_SERVICE_UNAVAILABLE = 503,
		STATUS_GATEWAY_TIMEOUT = 504,
		STATUS_HTTP_VERSION_NOT_SUPPORTED = 505
	};

	char *raw_post_data;
	size_t raw_post_size;
	bool post_is_www_form_encoded;
	field_map post_data;
	field_map request_fields;

protected:
	Socket *conn;
	RequestType req_type;
	std::string uri;
	field_map server_fields;

	BasicHttpDigestAuthInfo *auth_info;
	auth_sessions_map *auth_sessions;
	std::string auth_user;
	std::vector<std::string> paths_noauth;

	char *data;
	unsigned int data_len;
	bool must_free_data;
	bool digest_auth;

	void send_auth(std::string opaque, bool stale = false);

	// Returns true on failure
	bool check_authorization();

public:
	BasicHttpServer(Socket *conn);
	~BasicHttpServer() {
		if (must_free_data) delete [] data;
		if (raw_post_data) delete [] raw_post_data;
	}

	void process_request();
	void reply(StatusCode reply_code, std::string reply_msg);
	void begin_chunked_transfer();
	void send_chunk(const char *buf, size_t len);

	RequestType get_req_type() { return req_type; }
	std::string get_uri() { return uri; }
	std::string get_post_data(std::string &key) { return post_data[key]; }
	std::string get_auth_user() { return auth_user; }

	void set_content_type_html_utf8() {
		server_fields["Content-Type"] = "text/html; charset=UTF-8";
	}

	void set_content_type(std::string value) {
		server_fields["Content-Type"] = value;
	}

	void set_location(std::string value) {
		server_fields["Location"] = value;
	}

	void set_header(std::string name, std::string value) {
		server_fields[name] = value;
	}

	void set_data(std::string data);

	void set_data(void *buf, unsigned int len) {
		if (must_free_data) delete data;

		data_len = len;
		data = (char *) buf;
		must_free_data = false;
	}

	bool active() { return conn != 0; }

	void decode_complex_field(field_map &ret, std::string todecode);

	void enable_digest_auth(BasicHttpDigestAuthInfo &authinfo) {
		this->auth_info = &authinfo;
		this->auth_sessions = &authinfo.auth_sessions;
		digest_auth = true;
	}

	void add_noauth_path(std::string path) {
		paths_noauth.push_back(path);
	}

}; // BasicHttpServer

} // napespace tce_tools

#endif /* BASIC_HTTP_H_ */
